/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections.ObjectModel;

namespace DaveSexton.DocProject.Sandcastle
{
	public partial class VersionManagementDialog : Form
	{
		#region Public Properties
		public ICollection<SourceVersion> Versions
		{
			get
			{
				List<SourceVersion> sources = new List<SourceVersion>();

				if (versions.Count > 0)
				{
					foreach (ListViewItem item in sourceListView.Items)
					{
						string key = (string) item.Tag;

						foreach (SourceVersion version in versions[key])
							sources.Add(version);
					}
				}

				return sources.AsReadOnly();
			}
		}

		public IDictionary<string, string> SourceVersionNames
		{
			get
			{
				return sourceVersionNames;
			}
		}

		public bool HasChanges
		{
			get
			{
				return hasChanges;
			}
		}
		#endregion

		#region Private / Protected
		private ListViewItem SelectedSource
		{
			get
			{
				ListView.SelectedListViewItemCollection items = sourceListView.SelectedItems;

				if (items != null && items.Count > 0)
					return items[0];
				else
					return null;
			}
		}

		private bool hasChanges, suspendChangeTracking;
		private readonly IDictionary<string, string> sourceVersionNames;
		private readonly IDictionary<string, IList<SourceVersion>> versions;
		private readonly IDocProject project;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="VersionManagementDialog" /> class.
		/// </summary>
		public VersionManagementDialog(IEnumerable<SourceVersion> versions, IDictionary<string, string> sourceVersionNames, IDocProject project)
			: this()
		{
			if (versions == null)
				throw new ArgumentNullException("versions");

			if (sourceVersionNames == null)
				throw new ArgumentNullException("sourceVersionNames");

			if (project == null)
				throw new ArgumentNullException("project");

			this.versions = SourceVersion.GroupBy(true, v => v.SourceKey, versions, StringComparer.Ordinal);
			this.sourceVersionNames = new Dictionary<string, string>(sourceVersionNames);
			this.project = project;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="VersionManagementDialog" /> class.
		/// </summary>
		private VersionManagementDialog()
		{
			InitializeComponent();

			versionsDataGridView.AutoGenerateColumns = false;

			pathColumn.InitializeFileDialog += pathColumn_InitializeFileDialog;
			pathColumn.InitializeFolderDialog += pathColumn_InitializeFolderDialog;
		}
		#endregion

		#region Methods
		private void Inintialize()
		{
			foreach (Source source in project.AllSources)
			{
				SystemPath input = source.Input;

				if (input != SystemPath.Empty)
				{
					if (input.IsDirectory)
						AddFolderSource(input);
					else
						AddFileSource(input);
				}
			}

			if (sourceListView.Items.Count > 0)
				sourceListView.Items[0].Selected = true;
		}

		private void AddFolderSource(SystemPath source)
		{
			string image = "Folder";
			string group = "foldersListViewGroup";

			ListViewItem item = new ListViewItem(source.CompleteRelativeTo(project.Directory), image, sourceListView.Groups[group]);

			string key = SourceVersion.GenerateKey(project.Directory, source);

			if (!versions.ContainsKey(key))
				versions.Add(key, new List<SourceVersion>(0));

			item.Tag = key;

			item.SubItems.Add(string.Empty);		// version placeholder (unused for folder sources)

			if (sourceVersionNames.ContainsKey(key))
				item.SubItems.Add(sourceVersionNames[key]);
			else
				item.SubItems.Add(string.Empty);		// version name placeholder

			sourceListView.Items.Add(item);
		}

		private void AddFileSource(SystemPath source)
		{
			bool isAssembly = source.IsAssembly;

			string image = (isAssembly) ? "Assembly" : "Xml";
			string group = (isAssembly) ? "assembliesListViewGroup" : "reflectionListViewGroup";

			ListViewItem item = new ListViewItem(source.AssemblyNameOrCompletePath, image, sourceListView.Groups[group]);

			System.Reflection.AssemblyName name;

			if (source.TryGetAssemblyName(out name))
				item.SubItems.Add(name.Version.ToString());
			else
				item.SubItems.Add(string.Empty);		// version placeholder (unused for non-assembly sources)

			string key = SourceVersion.GenerateKey(project.Directory, source);

			if (!versions.ContainsKey(key))
				versions.Add(key, new List<SourceVersion>(0));

			item.Tag = key;

			if (sourceVersionNames.ContainsKey(key))
				item.SubItems.Add(sourceVersionNames[key]);
			else
				item.SubItems.Add(string.Empty);		// version name placeholder

			sourceListView.Items.Add(item);
		}

		private void InitializeForSource()
		{
			versionsDataGridView.SuspendLayout();

			try
			{
				ListViewItem sourceItem = SelectedSource;

				if (sourceItem == null)
				{
					versionsDataGridView.DataSource = null;
					versionsDataGridView.Tag = null;
					versionsDataGridView.Enabled = false;

					sourceVersionNameTextBox.Text = null;
					sourceVersionNameTextBox.Enabled = false;
				}
				else
				{
					if (versionsDataGridView.Tag != sourceItem)
					{
						suspendChangeTracking = true;

						try
						{
							pathColumn.CanSelectFolders = sourceItem.Group.Name == "foldersListViewGroup";
							
							string key = (string) sourceItem.Tag;

							if (sourceVersionNames.ContainsKey(key))
								sourceVersionNameTextBox.Text = sourceVersionNames[key];

							source.DataSource = versions[key];
							source.AllowNew = true;

							versionsDataGridView.DataSource = source;		// rebinding seems to be required
							versionsDataGridView.Tag = sourceItem;

							versionsDataGridView.Enabled = true;
							sourceVersionNameTextBox.Enabled = true;
						}
						finally
						{
							suspendChangeTracking = false;
						}
					}
				}
			}
			finally
			{
				versionsDataGridView.ResumeLayout(true);
			}
		}

		private bool ValidatePathCell(DataGridViewCell cell)
		{
			string value = (string) cell.FormattedValue;

			if (string.IsNullOrEmpty(value) || !PathFormat.IsValid(value, false))
			{
				errors.SetError(okButton, Resources.Text.InvalidPath);
				return false;
			}

			return true;
		}
		#endregion

		#region Event Handlers
		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);

			Inintialize();
		}

		protected override void OnValidating(CancelEventArgs e)
		{
			foreach (DataGridViewRow row in versionsDataGridView.Rows)
			{
				if (!ValidatePathCell(row.Cells[pathColumn.DisplayIndex]))
					e.Cancel = true;
			}

			if (!e.Cancel)
				errors.SetError(okButton, null);

			base.OnValidating(e);
		}

		private void pathColumn_InitializeFileDialog(object sender, DataGridViewFileColumnDialogEventArgs e)
		{
			e.Dialog.CheckFileExists = true;
			e.Dialog.Title = "Select an assembly or file";
			e.Dialog.Filter = PathFormat.InsertAllTypesDialogFilter(Resources.Text.AllAssembliesAndFilesDialogFilterPrefix,
				Resources.Text.AssemblyDialogFilter, Resources.Text.ReflectionFileDialogFilter);
		}

		private void pathColumn_InitializeFolderDialog(object sender, DataGridViewFileColumnFolderDialogEventArgs e)
		{
			e.Dialog.Description = "Select a folder that contains versioned sources";
		}

		private void okButton_Click(object sender, EventArgs e)
		{
			if (Validate(false))
				DialogResult = DialogResult.OK;
		}

		private void source_AddingNew(object sender, AddingNewEventArgs e)
		{
			ListViewItem sourceItem = SelectedSource;

			if (sourceItem != null)
			{
				SourceVersion version = new SourceVersion((string) sourceItem.Tag, project.Directory);
				e.NewObject = version;
			}
		}

		private void sourceListView_SelectedIndexChanged(object sender, EventArgs e)
		{
			InitializeForSource();
		}

		private void referencedVersionNameTextBox_TextChanged(object sender, EventArgs e)
		{
			if (suspendChangeTracking)
				return;

			ListViewItem sourceItem = SelectedSource;

			if (sourceItem != null)
			{
				string value = sourceVersionNameTextBox.Text.Trim();

				if (value.Length > 0)
				{
					sourceItem.SubItems[2].Text = value;
					sourceVersionNames[(string) sourceItem.Tag] = value;
				}
				else
				{
					sourceItem.SubItems[2].Text = string.Empty;
					sourceVersionNames.Remove((string) sourceItem.Tag);
				}

				hasChanges = true;
			}
		}

		private void referencedVersionNameTextBox_Enter(object sender, EventArgs e)
		{
			sourceVersionNameTextBox.SelectAll();
		}

		private void versionsDataGridView_UserAddedRow(object sender, DataGridViewRowEventArgs e)
		{
			hasChanges = true;
		}

		private void versionsDataGridView_UserDeletedRow(object sender, DataGridViewRowEventArgs e)
		{
			hasChanges = true;
		}

		private void versionsDataGridView_CellValueChanged(object sender, DataGridViewCellEventArgs e)
		{
			if (e.RowIndex > -1 && e.RowIndex != versionsDataGridView.NewRowIndex)
				hasChanges = true;
		}

		private void versionsDataGridView_KeyUp(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Escape && versionsDataGridView.CurrentRow.IsNewRow)
				versionsDataGridView.CancelEdit();
		}

		private void versionsDataGridView_CellEnter(object sender, DataGridViewCellEventArgs e)
		{
			// HACK: This event handler is a workaround for an EditOnEnter bug that prevents rows from being selected when a header is clicked.
			// This code is based on the workaround found here: https://connect.microsoft.com/VisualStudio/feedback/Workaround.aspx?FeedbackID=98504
			// The MouseClick event handler for the grid is also used.

			versionsDataGridView.EditMode = DataGridViewEditMode.EditOnEnter;
		}

		private void versionsDataGridView_MouseClick(object sender, MouseEventArgs e)
		{
			// See the grid's CellEnter event handler for more information about the purpose of this handler.

			DataGridView.HitTestInfo hit = versionsDataGridView.HitTest(e.X, e.Y);

			if (hit.Type == DataGridViewHitTestType.RowHeader)
			{
				versionsDataGridView.EditMode = DataGridViewEditMode.EditOnKeystrokeOrF2;
				versionsDataGridView.EndEdit();
			}
		}

		private void versionsDataGridView_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
		{
			if (e.ColumnIndex == sourceColumn.DisplayIndex)
			{
				DataGridViewCell cell = versionsDataGridView[e.ColumnIndex, e.RowIndex];

				string value = cell.Value as string;

				if (!string.IsNullOrEmpty(value))
				{
					cell.Value = new SystemPath(project.Directory, value, true).CompletePath;
				}
			}
		}

		private void versionsDataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e)
		{
			DataGridViewCell cell = versionsDataGridView[e.ColumnIndex, e.RowIndex];
			cell.ErrorText = null;

			if (e.ColumnIndex == sourceColumn.DisplayIndex)
			{
				string value = cell.Value as string;

				if (!string.IsNullOrEmpty(value) && System.IO.Path.IsPathRooted(value))
				{
					cell.Value = new SystemPath(value, true).CompleteRelativeTo(project.Directory);
				}
			}
		}
		#endregion
	}
}